﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using XcoAppSpaces.Security.Basic;
using test.XcoAppSpaces.Security.Basic.Helpers;
using XcoAppSpaces.Contracts.Security;
using Microsoft.Ccr.Core;
using XcoAppSpaces.Contracts.Service;
using XcoAppSpaces.Contracts.Exceptions;
using System.IO;
using System.Reflection;
using XcoAppSpaces.Contracts.Logging;
using System.Net;
using XcoAppSpaces.Contracts.Worker;

namespace test.XcoAppSpaces.Security.Basic
{
	[TestFixture]
	public class testXcoBasicSecurityService
	{
		private string role1;
		private string role2;
		private string role3;
		private XcoSecurityPermissionAttribute secAttr1;
		private XcoSecurityPermissionAttribute secAttr2;
		private XcoSecurityPermissionAttribute secAttr3;
		private string user1;
		private string pass1;
		private string address1;

		private XcoBasicSecurityService secService;
		private WorkerRegistryMock workerRegistryMock;
		private IXcoClientCredentials cred1;

		[SetUp]
		public void SetUp()
		{
			XcoLoggerFactory.CreateInstance = (t => new LoggerMockup());
			role1 = "role1";
			role2 = "role2";
			role3 = "role3";
			secAttr1 = new XcoSecurityPermissionAttribute(role1);
			secAttr2 = new XcoSecurityPermissionAttribute(role2);
			secAttr3 = new XcoSecurityPermissionAttribute(role3);
			user1 = "user1";
			pass1 = "pass1";
			address1 = "address1";

			workerRegistryMock = new WorkerRegistryMock();
			secService = new XcoBasicSecurityService();
			secService.Initialize(new ServiceRegistryMock(workerRegistryMock));
			secService.Start();
			cred1 = secService.GetClientCredentials(new NetworkCredential(user1, pass1), null);
		}

		[Test]
		public void TestCreateClientCredentials()
		{
			Assert.AreEqual(user1, cred1.User);
			Assert.AreEqual(PasswordUtil.CreatePasswordHash(pass1), cred1.Password);
		}

		[Test]
		public void TestCreateClientCredentials_NoUser()
		{
			IXcoClientCredentials cred = secService.GetClientCredentials(null, null);
			Assert.IsNull(cred);
		}

		[Test]
		public void TestCreateClientCredentials_NoPassword()
		{
			IXcoClientCredentials cred = secService.GetClientCredentials(new NetworkCredential(user1, null), null);
			Assert.AreEqual(user1, cred.User);
			Assert.IsNull(cred.Password);
		}


		[Test]
		public void TestAuthenticateAndAuthorize_Correct()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}}, 
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.AreNotEqual(Guid.Empty, authResult.SessionID);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
		}

		[Test]
		public void TestAuthenticate_NotAuthenticated()
		{
			IPort worker = new PortSet<int, string>();
			secService.AddUser(user1, "wrong", role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsFalse(authResult.IsAuthenticated);
			Assert.AreEqual(Guid.Empty, authResult.SessionID);
		}

		[Test]
		public void TestAuthenticate_Multiple()
		{
			IPort worker = new PortSet<int, string, MyType>();
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.AreNotEqual(Guid.Empty, authResult.SessionID);
			XcoAuthenticationResult authResult2 = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult2.IsAuthenticated);
			Assert.AreNotEqual(Guid.Empty, authResult2.SessionID);
			Assert.AreEqual(authResult.SessionID, authResult2.SessionID);
		}

		[Test]
		public void TestAuthorize_NotAuthorized()
		{
			IPort worker = new PortSet<int, string>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role2);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.AreNotEqual(Guid.Empty, authResult.SessionID);
			Assert.IsFalse(secService.Authorize(address1, authResult.SessionID, worker));
		}

		[Test]
		public void TestAuthorize_UnknownWorker()
		{
			IPort worker = new PortSet<int, string, MyType>();
			IPort worker2 = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.AreNotEqual(Guid.Empty, authResult.SessionID);
			Assert.IsFalse(secService.Authorize(address1, authResult.SessionID, worker2));
		}

		[Test]
		public void TestCheckPermission_Role1()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, "test", address1, authResult.SessionID);
			secService.CheckPermission(worker, 123, address1, authResult.SessionID);
			secService.CheckPermission(worker, new MyType(), address1, authResult.SessionID);
			secService.CheckPermission(worker, new MySubType(), address1, authResult.SessionID);
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_Role2_Error()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role2);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, "test", address1, authResult.SessionID);
		}

		[Test]
		public void TestCheckPermission_Role2_Correct()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role2);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, 123, address1, authResult.SessionID);
			secService.CheckPermission(worker, new MyType(), address1, authResult.SessionID);
			secService.CheckPermission(worker, new MySubType(), address1, authResult.SessionID);
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_UnknownSession()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, 123, address1, Guid.NewGuid());
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_UnknownWorker()
		{
			IPort worker = new PortSet<int, string, MyType>();
			IPort worker2 = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker2, 123, address1, authResult.SessionID);
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_UnknownAddress()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, 123, "unknownAddress", authResult.SessionID);
		}

		[Test]
		public void TestCheckPermission_Anonymous()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			
			XcoAuthenticationResult authResult = secService.Authenticate(address1, null);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, "test", address1, authResult.SessionID);
		}

		[Test]
		public void TestCheckPermission_Anonymous_Restrictive()
		{
			IPort worker = new PortSet<int, string, MyType>();
			secService.Policy = XcoSecurityPolicy.Restrictive;
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, null);
			Assert.IsFalse(authResult.IsAuthenticated);
			Assert.AreEqual(authResult.SessionID, Guid.Empty);
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_Anonymous_Error()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, null);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, 123, address1, authResult.SessionID);
		}

		[Test]
		public void TestLoadSaveUsers()
		{
			secService.AddUser(user1, pass1, role1);
			secService.SaveUsers();

			secService = new XcoBasicSecurityService(null);
			secService.Initialize(new ServiceRegistryMock(workerRegistryMock));
			secService.Start();
			
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, 123, address1, authResult.SessionID);
		}

		[Test]
		public void TestRemoveUser()
		{
			secService.AddUser(user1, pass1, role1);
			secService.RemoveUser(user1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsFalse(authResult.IsAuthenticated);
		}

		[Test]
		public void TestAddRole()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1);
			secService.AddRole(user1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
		}

		[Test]
		public void TestRemoveRole()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);
			secService.RemoveRole(user1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.IsFalse(secService.Authorize(address1, authResult.SessionID, worker));
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestAddUser_AlreadyExists()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);
			secService.AddUser(user1, pass1);
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestRemoveRole_UnknownUser()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);
			secService.RemoveRole("unknown", role1);
		}


		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestAddRole_UnknownUser()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);
			secService.AddRole("unknown", role1);
		}

		[Test]
		public void TestRemoveWorker()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(MyType)},
				new XcoServiceAttributeInfo{Attr = secAttr3, WorkerMessageType = typeof(MyType)}},
				PublishMode.Published);
			workerRegistryMock.FireWorkerRemoved(worker, PublishMode.Published);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, null);
			Assert.IsTrue(authResult.IsAuthenticated);
			Assert.IsFalse(secService.Authorize(address1, authResult.SessionID, worker));
		}

		[Test]
		public void TestUserStorePath()
		{
			//create security service and save users to file
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(XcoBasicSecurityService)).Location), "users.xml");
			if (File.Exists(path))
				File.Delete(path);
			XcoBasicSecurityService security = new XcoBasicSecurityService(path);
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();

			Assert.IsTrue(File.Exists(path));
		}

		[Test]
		public void TestUserStorePath2()
		{
			//create security service and save users to file
			string path = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(XcoBasicSecurityService)).Location), "users.xml");
			if (File.Exists(path))
				File.Delete(path);
			XcoBasicSecurityService security = new XcoBasicSecurityService();
			security.StorePath = path;
			security.AddUser("test", "123", "IntPortRole", "StrPortRole");
			security.SaveUsers();

			Assert.IsTrue(File.Exists(path));
		}

		[Test]
		[ExpectedException(typeof(XcoSecurityException))]
		public void TestCheckPermission_UserRemoved()
		{
			IPort worker = new PortSet<int, string, MyType>();
			workerRegistryMock.FireWorkerAdded(worker, new List<XcoServiceAttributeInfo>{
				new XcoServiceAttributeInfo{Attr = secAttr1, WorkerMessageType = null},
				new XcoServiceAttributeInfo{Attr = secAttr2, WorkerMessageType = typeof(int)}},
				PublishMode.Published);
			secService.AddUser(user1, pass1, role1);

			XcoAuthenticationResult authResult = secService.Authenticate(address1, cred1);
			Assert.IsTrue(secService.Authorize(address1, authResult.SessionID, worker));
			secService.CheckPermission(worker, "test", address1, authResult.SessionID);
			secService.RemoveUser(user1);
			secService.CheckPermission(worker, 123, address1, authResult.SessionID);
		}
	}
}
